Because document services are a subset of custom
services, the concepts described in this section apply to both custom
services and document services.
To implement a custom service, follow these steps:
1. | Create a service implementation class.
|
2. | Create a service contract.
|
3. | Implement data objects—if necessary. |
4. | Discover services.
|
Creating a Service Implementation Class
Service implementation classes contain the
business logic that can be published through service contracts for
consumption by external service clients.
You can use any X++ class as a service
implementation class; service implementation classes don’t need to
implement any interfaces or extend any super classes. A class definition
for a service implementation class MyDataObjectService could look like this.
public class MyDataObjectService
{
}
|
The following code sample illustrates how one of the service operations of MyDataObjectService
could look. All input and return parameters of service operations must
be either of primitive X++ types or of X++ classes that implement the
X++ interface AifXmlSerializable.
public MyDataObject CreateMyDataObject(str _s)
{
MyDataObject mdo; // see below for a definition of class MyDataObject
;
mdo = new MyDataObject();
mdo.parmMyString(_s);
// do something with 'mdo', for instance persist it ...
return mdo;
}
|
Creating a Service Contract
Service contracts define the subset of
functionality implemented by a service that can be published for
consumption by external service clients.
To create a new service contract, you need to create a new AOT node under the AOT node Services. In this example, the service contract is named MyDataObjectService, just like the service implementation class.
The newly created AOT node MyDataObjectService has a few properties that need to be initialized before external clients can consume the service:
(Service implementation) class This property links the service interface to the service implementation; in the preceding example, the value would be MyDataObjectService.
Security key Each service needs its own security key. The security key should have the same name as the service it protects; for example, MyDataObjectService should have the parent key Services in a functional area like Accounts Receivable. For this example, it makes sense to create a new security key, MyDataObjectService, without a parent key because the new service really doesn’t fit into a functional area.
Namespace
Optionally, you can specify the XML namespace that should be used in
the Web Services Description Language (WSDL). If the XML namespace isn’t
specified, http://tempuri.org is used by default.
External name Optionally, you can assign an external name for each service. In this example, the external name is left blank.
Finally, you need to add service operations to
the service contract. You do this by right-clicking the new AOT node and
selecting Add Operations. The service exposes only service operations
that have been explicitly added to the service interface.
Implementing Data Objects
Service operations can automatically use primitive X++ types (e.g., int and str)
as types for input and return parameters. X++ classes that are to be
used as data objects—that is, as input or return parameters for service
operations—must implement the interface AifXmlSerializable. Here’s an example.
public class MyDataObject implements AifXmlSerializable
{
str myString;
// more fields ...
#define.MyDataObjectNS ('http://schemas.contoso.com/samples/MyDataObject')
#define.MyDataObjectRoot ('MyDataObject')
}
|
Note
Axd<Document> classes implement the AifXmlSerializable interface and thus can be used as parameter types. |
You need to use AifXmlSerializable mainly to define the serialization to and the deserialization from XML for that class. Note that the methods serialize and deserialize
must be inverse functions because they use the same XML schema
definition. In other words, it must be possible to deserialize an XML
document that was created by serializing a data object, and vice versa.
The following code examples show what such
implementations can look like. For additional information about any of
the implemented methods, see the AIF information in the Microsoft
Dynamics AX 2009 SDK.
The method serialize
defines the serialization of the data object to XML. The code for
serializing the previously defined class to XML could look like this.
AifXml serialize()
{
str xml;
XmlTextWriter xmlTextWriter;
;
#Aif
xmlTextWriter = XmlTextWriter::newXml();
// turn off indentation to reduce file size
xmlTextWriter.formatting(XmlFormatting::None);
// initialize XML document
xmlTextWriter.writeStartDocument();
// write root element
xmlTextWriter.writeStartElement2(#MyDataObjectRoot, #MyDataObjectNS);
// write custom data
xmlTextWriter.writeElementString('MyString', myString);
// more fields ...
// serialize XML document into XML string
xmlTextWriter.writeEndDocument();
xml = xmlTextWriter.writeToString();
xmlTextWriter.close();
return xml;
}
|
The method deserialize
defines the deserialization of a data object from XML. The code for
deserializing an instance of the class defined from XML could look like
this.
void deserialize(AifXml xml)
{
XmlTextReader xmlReader;
;
xmlReader = XmlTextReader::newXml(xml);
// turn off whitespace handling to avoid extra reads
xmlReader.whitespaceHandling(XmlWhitespaceHandling::None);
xmlReader.moveToContent();
while ((xmlReader.nodeType() != XmlNodeType::Element) && !xmlReader.eof())
xmlReader.read();
xmlReader.readStartElement3(#MyDataObjectRoot, #MyDataObjectNS);
if (!xmlReader.eof() && xmlReader.isStartElement())
{
myString = xmlReader.readElementString3('MyString', #MyDataObjectNS);
// more fields ...
}
xmlReader.readEndElement();
xmlReader.close();
}
|
In X++, parm
methods are used to define properties. In data objects, all fields that
are used for serialization or deserialization must be accessible
through parm methods; they must also be optional and thus have a default value. Here’s an example.
public str parmMyString(str _myString = '')
{
if (!prmisdefault(_myString))
{
myString = _myString;
}
return myString;
}
|
The method getRootName returns the root name used for deriving names for service artifacts, as shown here.
public AifDocumentName getRootName()
{
return #MyDataObjectRoot;
}
|
The method getSchema returns the XML schema definition (XSD) that is used for serializing and deserializing the data object.
public AifXml getSchema()
{
str schema =
@'<?xml version="1.0"?>
<xsd:schema xmlns="http://schemas.contoso.com/samples/MyDataObject"
targetNamespace="http://schemas.contoso.com/samples/MyDataObject"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<xsd:complexType name="MyDataObjectType">
<xsd:sequence>
<xsd:element name="MyString" type="xsd:string" />
<!-- more fields ... -->
</xsd:sequence>
</xsd:complexType>
<xsd:element name="MyDataObject" type="MyDataObjectType"/>
</xsd:schema>'
;
return schema;
}
|
Tip
XML
schemas are used for validation, to avoid processing request messages
with invalid content, for example. To ensure that your XML schema
definition is valid, you should always use a data modeling tool or an
XML editor to generate the XSD rather than typing it manually. |
Discovering Custom Services
Once you’ve created a custom service, you need to register it with AIF. To register the custom service (in this example, MyDataObjectService) with AIF, expand the Services node in the AOT, select MyDataObjectService
and open its context menu, point to Add-Ins, and then click Register
Actions. Alternatively, you can register the service with AIF through
the AIF administration form Services, which is located in
Basic\Setup\Application Integration Framework. Click the Refresh button;
this refreshes all service entries and thus takes longer than using the
AOT Services node.
As a result of the registration, MyDataObjectService
shows up along with all other services and is now ready for use. It can
be published for external service clients to consume.